package me.itsoo.artemis.framework.repository.mapper;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import me.itsoo.artemis.framework.core.util.AnnotationUtils;
import me.itsoo.artemis.framework.core.util.Assert;
import me.itsoo.artemis.framework.core.util.ReflectUtils;
import me.itsoo.artemis.framework.repository.config.Configuration;
import me.itsoo.artemis.framework.repository.lang.Table;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * SqlMapperFactory
 *
 * @author zxy
 * @version 0.0.1
 * @since 2023/5/25
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SqlMapperFactory {

    private static final Map<Class<?>, SqlMapper<?>> MAPPER_CACHE = new HashMap<>(32);

    /**
     * 创建自定义 SqlMapper
     *
     * @param mapperType Class
     * @param config     Configuration
     * @param <T>        T
     * @return SqlMapper
     */
    @SuppressWarnings("unchecked")
    public static synchronized <T extends SqlMapper<?>> T getSqlMapper(Class<T> mapperType, Configuration config) {
        assertSqlMapperType(mapperType);

        return (T) MAPPER_CACHE.computeIfAbsent(mapperType, k -> getSqlMapper0(mapperType, config));
    }

    @SuppressWarnings("unchecked")
    private static <T extends SqlMapper<?>> T getSqlMapper0(Class<T> mapperType, Configuration config) {
        final T self = ReflectUtils.newInstance(mapperType);
        if (Objects.nonNull(self)) {
            self.setConfig(config);
            return (T) SqlMapperProxy.wrap(self);
        }

        return null;
    }

    private static <T> void assertSqlMapperType(Class<T> mapperType) {
        if (!SqlMapper.class.isAssignableFrom(mapperType)) {
            throw new IllegalArgumentException("'mapperType' must be SqlMapper.class");
        }
    }

    /**
     * 创建 DefaultSqlMapper
     *
     * @param dataType Class
     * @param config   Configuration
     * @param <T>      T
     * @return DefaultSqlMapper
     */
    @SuppressWarnings("unchecked")
    public static synchronized <T> DefaultSqlMapper<T> getDefaultSqlMapper(Class<T> dataType, Configuration config) {
        assertTableDataType(dataType);

        return (DefaultSqlMapper<T>) MAPPER_CACHE.computeIfAbsent(dataType, k -> getDefaultSqlMapper0(dataType, config));
    }

    @SuppressWarnings("unchecked")
    private static <T> DefaultSqlMapper<T> getDefaultSqlMapper0(Class<T> dataType, Configuration config) {
        final DefaultSqlMapper<T> self = new DefaultSqlMapper<>(dataType);
        self.setConfig(config);
        return (DefaultSqlMapper<T>) SqlMapperProxy.wrap(self);
    }

    private static <T> void assertTableDataType(Class<T> dataType) {
        final Table ann = AnnotationUtils.findAnnotation(dataType, Table.class);
        Assert.notNull(ann, "'dataType' is not an @Table class");
    }
}
